﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace ConsoleApplication8
{
	class Program
	{

		[MTAThread]
		static void Main( string[] args )
		{
			string id = "1";

			var prodOne = QueryHelper.Select<Product>().Where( p => p.Id == id ).ToList();
			// TranslateTo ==> FieldValue.fields.Where( f => f.Id == id );

			var first = QueryHelper.Select<Product>().FirstOrDefault();
			// TranslateTo ==> 
			//  FieldValue.fields.Where( f => f.Name.StartsWith( "Catalog.Product" ) && FieldValue.fields.FirstOrDefault().Id == f.Id );

			var prodCheaper = QueryHelper.Select<Product>().Where( p => p.Price >= 1 && p.Price <= 20 );
			// TranslateTo ==> 
			// var index = FieldValue.fields.Where( f => f.Name == "Catalog.Product[Price]" && f.Value >= "1" && f.Value <= "20" );
			// FieldValue.fields.Where( f => f.Name.StartsWith( "Catalog.Product" ) && index.Any( i => i.Id == f.Id ) );

			var prodByBrand = QueryHelper.Select<Product>().Where( p => p.Brand.Name == "sony" );
			// TranslateTo ==> 
			//  var brandsOf = FieldValue.fields.Where(f=>f.Name=="Catalog.Brand[Name]" && f.Value == "sony");
			//  var relation = DataObjectRelationship.relations.Where(r=>r.EntityName=="Catalog.Brand" && r.EntityName=="Catalog.Product[Brand]" && brandsOf.Any( f=>f.Id == r.ChildId));
			//  FieldValue.fields.Where( f => f.Name == "Catalog.Product" && relation.Any( r => r.Id == f.Id ) );
			
		}
	}

	public static class QueryHelper
	{

		public static IQueryable<T> Select<T>()
		{
			var provider = new DynQueryProvider();
			return provider.CreateQuery<T>( Expression.Constant( Enumerable.Empty<Product>().AsQueryable() ) );
		}


		public static Expression GetBody( Expression<Func<object>> exp )
		{
			//return exp.Body;
			return StripQuoteAndLambda( exp );
		}

		public static Expression StripQuoteAndLambda( Expression exp )
		{
			Expression current = exp;

			while ( current.NodeType == ExpressionType.Quote
				|| current.NodeType == ExpressionType.Lambda )
			{
				if ( current.NodeType == ExpressionType.Quote )
					current = ( ( UnaryExpression )current ).Operand;
				else
					current = ( ( LambdaExpression )current ).Body;
			}

			return current;
		}

		public static TExpression Find<TExpression>( this Expression exp )
			where TExpression : Expression
		{
			var finder = new FindExpression<TExpression>();
			finder.Find( exp );
			return ( TExpression )finder.ExpressionFound;
		}
	}

	public class Product
	{
		public string Id
		{ get; set; }

		public string Name
		{ get; set; }

		public double Price
		{ get; set; }

		public DateTime CreateDate
		{ get; set; }

		public Brand Brand
		{ get; set; }
	}

	public class Brand
	{
		public string Id
		{ get; set; }

		public string Name
		{ get; set; }
	}

	public class FieldValue
	{
		public static readonly List<FieldValue> fields =
			new List<FieldValue>()
			{
				new FieldValue() { Name = "Catalog.Product[Name]", Id="1", Value="IPhone"},
				new FieldValue() { Name = "Catalog.Product[Price]", Id= "1", Value="10.5"},
				new FieldValue() { Name = "Catalog.Product[CreateDate]", Id= "1", Value="1/1/2012"},
				new FieldValue() {Name = "Catalog.Product[Custom]", Id="1", Value="sku-1"},

				new FieldValue() { Name = "Catalog.Product[Name]", Id="2", Value="IPhone 2"},
				new FieldValue() { Name = "Catalog.Product[Price]", Id= "2", Value="22.234"},
				new FieldValue() { Name = "Catalog.Product[CreateDate]", Id= "2", Value="2/2/2012"},

				new FieldValue() { Name = "Catalog.Product[Name]", Id="3", Value="IPhone 3"},
				new FieldValue() { Name = "Catalog.Product[Price]", Id= "3", Value="33.234"},
				new FieldValue() { Name = "Catalog.Product[CreateDate]", Id= "3", Value="3/3/2012"},

				new FieldValue() { Name = "Catalog.Product[Name]", Id="4", Value="IPhone 4"},
				new FieldValue() { Name = "Catalog.Product[Price]", Id= "4", Value="44.234"},
				new FieldValue() { Name = "Catalog.Product[CreateDate]", Id= "4", Value="4/4/2012"},

				new FieldValue() { Name = "Catalog.Brand[Name]", Id="1", Value = "Apple"},
				new FieldValue() { Name = "Catalog.Brand[Name]", Id="2", Value = "Nokia"},

				new FieldValue() { Name = "Catalog.Tag[Name]", Id="1", Value = "Pré-Venda"},
				new FieldValue() { Name = "Catalog.Tag[Name]", Id="2", Value = "Mais Vendido"},
				new FieldValue() { Name = "Catalog.Tag[Name]", Id="3", Value = "Promocao"}
			};

		public string Name
		{ get; set; }

		public string Id
		{ get; set; }

		public string Value
		{ get; set; }
	}

	public class DataObjectRelationship
	{
		public static readonly List<DataObjectRelationship> relations =
			new List<DataObjectRelationship>()
			{
				new DataObjectRelationship() { FieldName = "Catalog.Product[Brand]", Id="1", Order=1, EntityName="Catalog.Brand", ChildId="1"},
				new DataObjectRelationship() { FieldName = "Catalog.Product[Brand]", Id="2", Order=1, EntityName="Catalog.Brand", ChildId="2"},
				new DataObjectRelationship() { FieldName = "Catalog.Product[Brand]", Id="3", Order=1, EntityName="Catalog.Brand", ChildId="1"},

				new DataObjectRelationship() { FieldName = "Catalog.Product[Tag]", Id="1", Order=1, EntityName="Catalog.Tag", ChildId="1"},
				new DataObjectRelationship() { FieldName = "Catalog.Product[Tag]", Id="1", Order=2, EntityName="Catalog.Tag", ChildId="2"},
				new DataObjectRelationship() { FieldName = "Catalog.Product[Tag]", Id="1", Order=3, EntityName="Catalog.Tag", ChildId="2"}
			};

		public string FieldName
		{ get; set; }

		public string Id
		{ get; set; }

		public int Order
		{ get; set; }

		public string EntityName
		{ get; set; }

		public string ChildId
		{ get; set; }
	}

	public class DynQuery<TElement> : IQueryable<TElement>
	{
		public DynQuery( IQueryProvider provider )
			: this( null, provider )
		{ }

		public DynQuery( Expression expression, IQueryProvider provider )
		{
			this.Expression = expression ?? Expression.Constant( this );
			this.Provider = provider;
		}

		#region IEnumerable<TElement> Members

		public IEnumerator<TElement> GetEnumerator()
		{
			return this.Provider.Execute<IEnumerable<TElement>>( this.Expression ).GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			throw new NotImplementedException();
		}

		#endregion

		#region IQueryable Members

		public Type ElementType
		{
			get { return typeof( TElement ); }
		}

		public System.Linq.Expressions.Expression Expression
		{
			get;
			private set;
		}

		public IQueryProvider Provider
		{
			get;
			private set;
		}

		#endregion
	}



	public class DynQueryProvider : IQueryProvider
	{

		#region IQueryProvider Members

		public IQueryable<TElement> CreateQuery<TElement>( System.Linq.Expressions.Expression expression )
		{
			return new DynQuery<TElement>( expression, this );
		}

		public IQueryable CreateQuery( System.Linq.Expressions.Expression expression )
		{
			throw new NotSupportedException();
		}

		public TResult Execute<TResult>( System.Linq.Expressions.Expression expression )
		{
			var executor = new QueryExecutor();

			executor.Execute( expression );

			return default( TResult );
		}

		public object Execute( System.Linq.Expressions.Expression expression )
		{
			throw new NotImplementedException();
		}

		#endregion
	}


	public class QueryExecutor : ExpressionVisitor
	{
		private SymbolTable symbols = new SymbolTable();

		public IQueryTranslator Translator
		{
			get;
			set;
		}

		public void Execute( Expression query )
		{
			if ( this.Translator == null )
				this.Translator = new QueryTranslator( this.symbols );

			this.Visit( query );
		}

		protected override Expression VisitMethodCall( MethodCallExpression node )
		{
			if ( node.Method.DeclaringType == typeof( Queryable ) )
			{
				if ( node.Method.Name == "Where" )
				{
					return Expression.Call(
						node.Method,
						this.DeclareEntity( node.Arguments[ 0 ] ),
						this.Translator.Translate( node.Arguments[ 1 ] ) );
						//this.Translator.FromWhere( node.Arguments[ 1 ] ) );
					//return this.Translator.FromWhere( node.Arguments[ 1 ] );
				}
			}
			return base.VisitMethodCall( node );
		}

		private Expression DeclareEntity( Expression source )
		{
			Symbol symbol;

			if ( this.symbols.TryGetSymbol( source, out symbol ) )
				return symbol.Reference;
			else
				return this.symbols.DeclEntity( source, QueryHelper.GetBody( () => FieldValue.fields ) ).Reference;
		}
	}


	public class SymbolTable
	{
		private Dictionary<Expression, Symbol> symbols = new Dictionary<Expression, Symbol>();


		public bool TryGetSymbol( Expression source, out Symbol symbol )
		{
			return this.symbols.TryGetValue( source, out symbol );
		}

		public Symbol DeclEntity( Expression source, Expression reference )
		{
			Symbol s = new Symbol() { Type = SymbolType.Entity, Source = source, Reference = reference };
			this.symbols[ source ] = s;
			return s;
		}
	}

	public class Symbol
	{
		public SymbolType Type
		{ get; set; }

		public Expression Source
		{ get; set; }

		public Expression Reference
		{ get; set; }
	}

	public enum SymbolType
	{
		Entity,
		Relationship,
		Index
	}

	public interface IQueryTranslator
	{
		Expression Translate( Expression node );

		Expression FromWhere( Expression node );

		Expression TakeFirst( Expression node );
	}

	public class QueryTranslator : ExpressionVisitor, IQueryTranslator
	{

		public QueryTranslator( SymbolTable symbols )
		{
			this.Symbols = symbols;
		}

		public SymbolTable Symbols
		{ get; private set; }


		public Expression Translate( Expression node )
		{
			return base.Visit( node );
		}


		//protected override Expression VisitParameter( ParameterExpression node )
		//{
		//    return this.GetParameter( node );
		//    //return base.VisitParameter( node );
		//}

		protected override Expression VisitMember( MemberExpression node )
		{
		    var parameter = this.GetParameter( node);
		    var property = node.Member.Name;

		    Expression
		        .MakeMemberAccess(
		            parameter,
		            typeof( FieldValue ).GetProperty( "Name" ) );
		            //return base.VisitMember( node );
		}

		public virtual Expression FromWhere( Expression node )
		{

			return node;
		}

		public Expression TakeFirst( Expression node )
		{
			throw new NotImplementedException();
		}


		protected Expression GetParameter( Expression source )
		{
			var node = source.Find<ParameterExpression>();

			Symbol symbol;
			if ( this.Symbols.TryGetSymbol( node, out symbol ) )
				return symbol.Reference;
			else
				return this.Symbols.DeclEntity( node, Expression.Parameter( typeof( FieldValue ), node.Name ) ).Reference;
		}
	}


	public class FindExpression<TType> : ExpressionVisitor
		where TType : class
	{
		public FindExpression()
			: this( null )
		{ }

		public FindExpression(Func<TType, bool> condition)
		{
			this.Condition = condition ?? ( t => true );
		}

		public Func<TType, bool> Condition
		{
			get;
			private set;
		}

		public Expression ExpressionFound
		{ get; private set; }

		public bool Found
		{ get { return this.ExpressionFound != null; } }

		public bool Find( Expression lookAt )
		{
			this.ExpressionFound = null;
			this.Visit( lookAt );

			return this.Found;
		}

		public override Expression Visit( Expression node )
		{
			if ( !this.Found )
			{
				if ( node is TType )
					if ( this.Condition( node as TType ) )
						this.ExpressionFound = node;

				if ( !this.Found )
					return base.Visit( node );
			}
			return node;
		}
	}


}
